package cn.leaf.calculator.datasource;

import cn.leaf.calculator.CalException;
import cn.leaf.calculator.bean.CalUnitEntity;
import cn.leaf.calculator.bean.FormulationCalUnitEntity;

import cn.leaf.calculator.bean.ParamEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 抽象的计算数据源
 *
 * @author 李克国
 * @version 1.0.0

 */
@Slf4j
public abstract class AbstractCalDataSources<T> implements CalDataSources<T> {

    /**
     * 计算池
     */
    private List<FormulationCalUnitEntity<T>> calEntities;

    /**
     * 参数池
     */
    private Params<T> parameters;

    /**
     * 转换器
     */
    private Converter<T, ? extends FormulationCalUnitEntity<T>> converter;

    private Iterator<FormulationCalUnitEntity<T>> iterator;

    public void initialize() {
        // 获取数据源
        initializeCalUnitEntity();
        // 校验
        validate();
        // 准备
        preprocess();
        // 参数初始化
        initializeParameters();
        // 装配参数
        iterator = calEntities.iterator();
    }

    private void validate() {
        Assert.notNull(calEntities, "CalDataSourcesInitializeException: 计算单元不能为空");
        calEntities.forEach(o -> {
            if (!o.getFormulaResolver().validate()) {
                throw new CalException("CalDataSourcesInitializeException: 数据源初始化失败，公式" + o.getSourceFormula() + "不合法");
            }
        });
        log.info("计算数据源：校验通过");
    }

    /**
     * 分析依赖关系，标注计算单元状态，若参数依赖在本次计算单元中，装配其参数
     */
    private void preprocess() {
        parameters = new Params<>();
        calEntities.forEach(o -> {
            o.getParamEntityList().stream()
                    .filter(p -> hasCurrentCalUnits(p.getId()) != null)
                    .collect(Collectors.toList())
                    .forEach(t -> {
                        o.updateState(CalUnitEntity.CAL_WAIT);
                        o.setParameters(Objects.requireNonNull(hasCurrentCalUnits(t.getId())));
                    });
            if (CalUnitEntity.CAL_INIT == o.getState()) {
                // 标记 为初始化完成
                o.updateState(CalUnitEntity.CAL_INIT_ED);
            }
            // 加入本地参数缓存池
            parameters.add(o);
        });
        log.info("计算数据源：计算单元准备完成");
    }

    /**
     * 初始化计算单元
     */
    private void initializeCalUnitEntity() {
        calEntities = getFormulaCalEntity().stream()
                .map(e -> converter.convert(e))
                .collect(Collectors.toList());
        calEntities.forEach(FormulationCalUnitEntity::initialize);
        log.info("计算数据源：计算单元初始化完成");
    }


    @Override
    public List<T> getCalResult() {
        return calEntities.stream()
                .filter(e -> CalUnitEntity.CAL_OVER == e.getState())
                .map(ParamEntity::getBodyEntity)
                .collect(Collectors.toList());
    }

    @Override
    public void clearParameters() {
        parameters.clear();
    }

    @Override
    public void clearCalUnit() {
        calEntities.clear();
    }

    @Override
    public void add(FormulationCalUnitEntity<T> unitEntity) {
        calEntities.add(unitEntity);
        parameters.add(unitEntity);
        initializeParameters();
    }

    @Override
    public void remove(Serializable id) {
        calEntities.removeIf(c -> c.getId().equals(id));
    }

    @Override
    public void setConverter(Converter<T, ? extends FormulationCalUnitEntity<T>> converter) {
        this.converter = converter;
    }

    @Override
    public boolean hasNext() {
        return iterator.hasNext();
    }

    @Override
    public FormulationCalUnitEntity<T> next() {
        return iterator.next();
    }

    private void initializeParameters() {
        // 统计外部依赖参数
        List<Serializable> list = new ArrayList<>();
        calEntities.forEach(c -> c.getParamEntityList().forEach(p -> {
            if (p.getState() == ParamEntity.PARAM_INIT) {
                list.add(p.getId());
            }
        }));
//        calEntities.forEach(c -> parameters.add(c.getParamEntityList()));
//        List<ParamEntity<T>> collect = parameters.toList().stream()
//                .filter(p -> ParamEntity.PARAM_INIT == p.getState() && p.getValue() == null)
//                .collect(Collectors.toList());
        // 获取外部依赖参数
        List<T> updateParamEntity = updateParamEntity(list);
        // 装配参数
        updateParamEntity.forEach(e -> {
            FormulationCalUnitEntity<T> param = converter.convert(e);
            calEntities.forEach(c -> {
                if (c.hasParameters(Objects.requireNonNull(param).getId())) {
                    c.setParameters(param);
                }
            });
            parameters.add(Objects.requireNonNull(param));
        });
        log.info("计算数据源：外部参数装配完成");
    }

    /**
     * id项是否在当前计算区
     * 条件为 {@link CalUnitEntity#CAL_INIT } 或者{@link CalUnitEntity#CAL_INIT_ED }
     * 并且 id匹配
     *
     * @param id id
     * @return 如果id存在：true 否则 false
     */
    private FormulationCalUnitEntity<T> hasCurrentCalUnits(Serializable id) {
        for (FormulationCalUnitEntity<T> c : calEntities) {
            if (c.getId().equals(id) && (CalUnitEntity.CAL_INIT == c.getState() || CalUnitEntity.CAL_INIT_ED == c.getState())) {
                return c;
            }
        }
        return null;
    }

    static class Params<T> {
        private Map<Serializable, FormulationCalUnitEntity<T>> params;

        public Params() {
            this.params = new HashMap<>();
        }

        public void add(FormulationCalUnitEntity<T> param) {
            if (!this.params.containsKey(param.getId())) {
                this.params.put(param.getId(), param);
                // 忽略新的元素
            }
        }

        public void add(Collection<? extends FormulationCalUnitEntity<T>> params) {
            params.forEach(this::add);
        }

        public List<ParamEntity<T>> toList() {
            List<ParamEntity<T>> list = new ArrayList<>();
            list.addAll(params.values());
            return list;
        }

        public void clear() {
            params.clear();
        }


    }
}
